/*    Copyright 2010 Tobias Marschall
 *
 *    This file is part of MoSDi.
 *
 *    MoSDi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    MoSDi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with MoSDi.  If not, see <http://www.gnu.org/licenses/>.
 */

package mosdi.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import mosdi.fa.Alphabet;
import mosdi.util.iterators.SegmentationGenerator;

/** Represents a set of constraints for IUPAC strings. */
public class IupacStringConstraints {
	private int[] minFrequencies;
	private int[] maxFrequencies;
	// charClasses = {{'A', 'C', 'G', 'T'},{'R', 'Y', 'S', 'W', 'K', 'M'},{'B', 'D', 'H', 'V'},{'N'}}; 
	
	public IupacStringConstraints(int length, boolean wildcardsAllowed) {
		minFrequencies = new int[4];
		maxFrequencies = new int[4];
		if (wildcardsAllowed) {
			Arrays.fill(maxFrequencies, length);
		} else {
			maxFrequencies[0] = length;
		}
	}

	/** Disallow all wildcards besides the given number of N characters. */
	public IupacStringConstraints(int length, int numberOfNs) {
		minFrequencies = new int[4];
		maxFrequencies = new int[4];
		maxFrequencies[0] = length;
		maxFrequencies[3] = numberOfNs;
	}

	public IupacStringConstraints(int[] minFrequencies, int[] maxFrequencies) {
		if ((minFrequencies.length!=4) || (maxFrequencies.length!=4)) throw new IllegalArgumentException();
		this.minFrequencies = minFrequencies;
		this.maxFrequencies = maxFrequencies;
	}
	
	public IupacStringConstraints(int[] maxFrequencies) {
		if (maxFrequencies.length!=4) throw new IllegalArgumentException();
		this.minFrequencies = new int[4];
		this.maxFrequencies = maxFrequencies;
	}

	public int[] getMinFrequencies() {
		return minFrequencies;
	}

	public int[] getMaxFrequencies() {
		return maxFrequencies;
	}

	public IupacStringConstraints minus(int charIndex) {
		int[] minFrequency = Arrays.copyOf(this.minFrequencies, 4);
		int[] maxFrequency = Arrays.copyOf(this.maxFrequencies, 4);
		int charClass = Iupac.getCharacterMultiplicity(charIndex)-1;
		if (minFrequency[charClass]>0) minFrequency[charClass]-=1;
		if (maxFrequency[charClass]==0) {
			throw new IllegalArgumentException("Tried to substract a forbidden character.");
		}
		maxFrequency[charClass]-=1;
		return new IupacStringConstraints(minFrequency, maxFrequency);
	}
	
	/** Returns an array of all characters (in lexicographical order) that 
	 *  can appear in a string of given length that satisfies the constraints.
	 */
	public int[] validFirstCharacters(int length) {
		final Alphabet iupacAlphabet = Alphabet.getIupacAlphabet();
		int minLength = 0;
		for (int i : minFrequencies) minLength+=i;
		if (minLength>length) return new int[0];
		int[] chars = new int[iupacAlphabet.size()];
		int n = 0;
		for (int c=0; c<iupacAlphabet.size(); ++c) {
			int charClass = Iupac.getCharacterMultiplicity(c)-1;
			if (maxFrequencies[charClass]<=0) continue;
			if ((minLength==length) && (minFrequencies[charClass]==0)) continue;
			chars[n++]=c;
		}
		return Arrays.copyOf(chars, n);
	}
	
	public boolean check(String iupacString) {
		int[] profile = new int[4]; 
		for (int i=0; i<iupacString.length(); ++i) {
			int charClass = Iupac.getCharacterMultiplicity(iupacString.charAt(i))-1;
			profile[charClass]+=1;
		}
		for (int i=0; i<4; ++i) {
			if (profile[i]<minFrequencies[i]) return false;
			if (profile[i]>maxFrequencies[i]) return false;
		}
		return true;
	}
	
	/** Returns a list of characters that can possibly appear in a valid string. */
	public List<Character> getValidCharacters() {
		List<Character> result = new ArrayList<Character>();
		final Alphabet iupacAlphabet = Alphabet.getIupacAlphabet();
		for (int i=0; i<iupacAlphabet.size(); ++i) {
			if (maxFrequencies[Iupac.getCharacterMultiplicity(i)-1]>0) {
				result.add(iupacAlphabet.get(i));
			}
		}
		return result;
	}
	
	/** Returns the number of strings of a given length that comply with 
	 *  the constraints. */
	public long validStringCount(int length) {
		long result = 0;
		for (int[] segmentation : new SegmentationGenerator(length, maxFrequencies, minFrequencies)) {
			AbelianPattern ab = new AbelianPattern(segmentation);
			long n = ab.instanceCount();
			n*=Math.pow(4, segmentation[0]);
			n*=Math.pow(6, segmentation[1]);
			n*=Math.pow(4, segmentation[2]);
			result+=n;
		}
		return result;

	}
	
}
